#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/time.h>
#include "logger.h"
#include <math.h>

#define NUMBER_LENGTH 7

int array_length = 0;

void helper() {
  printf("This programm handles integers and only them.\n");
  printf("Put these numbers to file separated by a comma.\n"); 
  printf("Put the name of this file after name of exe file in command line.\n");
  printf("After this put name of file in which you would like to store result of this programm.\n");
  printf("After name of file you can put log level. It could be an integer between 0 and 2.\n");
}

int handle_command_line(int argc, char** argv) {
  int log_status;
  if (4 == argc) {
    if ((0 <= atoi(argv[3])) && (atoi(argv[3]) <= 2)) {
      log_status = Log_Init(atoi(argv[3]));
      if (-1 == log_status) {
        printf("%s\n", "Cannot Init log");
        return -1;
      }
      Log_Write(0, "Programm started");
    } else {
      printf("%s\n", "Incorrect input. Try to use ./a.out --help");
      return -1;
    }
  } else {
    log_status = Log_Init(0);
    if (-1 == log_status) {
      printf("%s\n", "Cannot Init log");
      return -1;
    }
    Log_Write(0, "Programm started");
  }

  if (1 == argc) {
    printf("There is no input data. Try to use ./a.out --help\n");
    return -1;
  }
  if ((2 == argc) && (0 == strcmp("--help", argv[1]))) {
    helper();
    return -1;
  }

  if (argc < 3) {
    printf("%s\n", "Incorrect input. Try to use ./a.out --help");
    return -1;
  }
  return 0;
}

int open_file(char* filename, int flag) {
  int fd = open(filename, flag, 0666);
  if (-1 == fd) {
    Log_Write(2, "Cann't open file");
    close(fd);
    return -1;
  }
  Log_Write(0, "File opened");
  return fd;
}

char* map_file(int fd) {
  struct stat buf;
  int file_stat = fstat(fd, &buf);
  if (-1 == file_stat) {
    Log_Write(2, "Cann't get information about file");
    close(fd);
    return "Error";
  }
  int size = (int) buf.st_size;

  char* map;
  map = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
  if (MAP_FAILED == map) {
    Log_Write(2, "Error mmaping the fail");
    close(fd);
    return "Error";
  }
  Log_Write(0, "File mapped");
  return map;
}

int* parse_array(char* map) {
  int block_size = 1000;
  int* arr = malloc(block_size * sizeof(int));
  if (NULL == arr) {
    Log_Write(2, "Cann't malloc arr");
    return NULL;
  }
  char number[NUMBER_LENGTH];
  int k = 0, i = 0, j = 0;
  number[0] = '\0';
  int file_length = strlen(map);
  for (i = 0;i < file_length;i++) {
    if (('0' <= map[i]) && ('9' >= map[i])) {
      number[k] = map[i];
      k++;
      if (NUMBER_LENGTH == k) {
        Log_Write(1, "You input number bigger than integer. Try to use ./a.out --help");
        free(arr);
        return NULL;
      }
    } else if (',' == map[i]) {
      if (',' == map[i-1]) {
        Log_Write(2, "Bad input.");
        return NULL;
      }
      if (file_length - 1 == i) {
        continue;
      }
      number[k] = '\0';
      arr[j] = atoi(number);
      j++;
      number[0] = '\0';
      k = 0;
      if (NULL == arr) {
        Log_Write(2, "Cann't realloc arr");
        free(arr);
        return NULL;
      }
    } else {
      Log_Write(1, "File includes not only numbers and commas. Try to use ./a.out --help");
      free(arr);
      return NULL;
    }
    if (block_size == j) {
      block_size = block_size + 1000;
      arr = realloc(arr, block_size * (sizeof(int)));
    }
  }

  number[k] = '\0';
  arr[j] = atoi(number);
  array_length = j+1;
  Log_Write(0, "Parsing finished");
  return arr;
}

int compare (const void * a, const void * b) {
  return ( *(int*)a - *(int*)b );
}

int* sort_array(int* arr, int size) {
  qsort(arr, size, sizeof(int), compare);
  Log_Write(0, "Sort finished");
  return arr;
}

char* write_array_to_map(char* map, int* arr) {
  int position = 0, i = 0;
  for (i = 0;i < array_length;i++) {
    if (array_length - 1 == i) {
      position = position + sprintf(map + position, "%d", arr[i]);
      } else {
      position = position + sprintf(map + position, "%d,", arr[i]);
    }
  }
  free(arr);
  Log_Write(0, "Writing array to map finished");
  return map;
}

char* munmap_array(char* map) {
  int munmap_result = munmap(map, strlen(map));
  if (-1 == munmap_result) {
    Log_Write(2, "Munmapping Error");
    return NULL;
  }
  Log_Write(0, "Munmapping finished");
  return "Success";
}

int main(int argc, char* argv[]) {
  struct timeval tv;
  struct timezone tz;
  gettimeofday(&tv, &tz);
  time_t start = tv.tv_sec;
  int handling_command_line = handle_command_line(argc, argv);
  if (-1 == handling_command_line) {
    return 0;
  }

  int fd1 = open_file(argv[1], O_RDWR);
  if (-1 == fd1) {
    return 0;
  }

  char* map1 = map_file(fd1);
  if (0 == strcmp("Error", map1)) {
    return 0;
  }

  int* arr = parse_array(map1);
  if (NULL == arr) {
    close(fd1);
    return 0;
  }

  arr = sort_array(arr, array_length);

  int fd2 = open_file(argv[2], O_RDWR|O_CREAT|O_TRUNC);
  if (-1 == fd2) {
    return 0;
  }

  if (ftruncate(fd2, strlen(map1)) < 0) {
    Log_Write(2, "Ftruncate error");
    return 0;
  }

  char* map2 = map_file(fd2);
  if (0 == strcmp("Error", map2)) {
    return 0;
  }

  map2 = write_array_to_map(map2, arr);

  char* result1 = munmap_array(map1);
  if (NULL == result1) {
    close(fd1);
    return 0;
  }

  char* result2 = munmap_array(map2);
  if (NULL == result2) {
    close(fd2);
    close(fd1);
    return 0;
  }

  close(fd2);
  close(fd1);
  Log_End();
  gettimeofday(&tv, &tz);
  time_t end = tv.tv_sec;
  printf("%d\n", (int) (end - start));
  return 0;
}
